/*
 * Copyright (c) 2011 Obix Labs Limited
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 
 * 		Redistribution of source code must retain the above 
 * 		copyright notice, this list of conditions and the 
 * 		following disclaimer.
 *
 * 		Redistribution in binary form must reproduce the 
 * 		above copyright notice, this list of conditions 
 * 		and the following disclaimer in the documentation 
 * 		and/or other materials provided with the distribution.
 * 
 * 	THIS SOFTWARE IS PROVIDED "AS IS," WITHOUT A WARRANTY OF 
 * 	ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS 
 * 	AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * 	FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, 
 * 	ARE HEREBY EXCLUDED. OBIX LABS LIMITED ("Obix Labs") AND ITS 
 * 	LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE 
 * 	AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR 
 * 	ITS DERIVATIVES. IN NO EVENT WILL Obix Labs OR ITS LICENSORS BE 
 * 	LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, 
 * 	INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 
 * 	DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF 
 * 	LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS 
 * 	SOFTWARE, EVEN IF Obix Labs HAS BEEN ADVISED OF THE POSSIBILITY OF 
 * 	SUCH DAMAGES.
 */
package com.obixlabs.commons.security;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * <p>
 * A special purpose buffer which not only encapsulates a password, but enables
 * {@link PasswordRequirement rules} to modify/test the password. This type provides
 * special facilities for read-only fields (i.e. non-writable fields) and password size restriction. 
 * </p> 
 * <p>
 * Read-only indices form a safety mechanism that ensure that {@link PasswordRequirement rules}
 * do not overwrite each others modifications to the buffer. Although the size restriction on the buffer can 
 * itself be interpreted as a rule, it is actually provided to ensure that {@link PasswordRequirement rules}
 * do not continuously attempt to modify the buffer, even when there are no empty indices left. <b>Note</b> 
 * that a maximum size does not necessarily have to be specified.
 * </p>
 */
public class PasswordBuffer implements Serializable
{
	private static final long serialVersionUID = 5399654560435033622L;

	/**
	 * <p>
	 * Internal {@link StringBuffer buffer} which holds password.
	 * </p>
	 */
	private StringBuffer internalBuffer;	
	
	/**
	 * <p>
	 * The list of read-only indices in this buffer. The read-only index facility is 
	 * provided by this class to ensure that {@link PasswordRequirement rules} do
	 * not overwrite each others modifications to the buffer.
	 * </p>
	 */
	private List<Integer> nonWritableIndices;
	
	/**
	 * <p>
	 * The maximum size of this buffer.
	 * </p>
	 */
	private int maximumSize;
	
	/**
	 * <p>
	 * Default constructor.
	 * </p>
	 */
	public PasswordBuffer()
	{this(-1);}
	
	/**
	 * <p>
	 * Creates a buffer of the specified maximum size. When the buffer reaches 
	 * this {@link #length() size}, all subsequent attempts to {@link #append(char)}
	 * characters to it will fail if there are no {@link #getWritableIndices() writable indices} 
	 * left in the instance.
	 * </p>
	 * 
	 * @param maximumSize  The maximum permissible size of the buffer.
	 */
	public PasswordBuffer(int maximumSize)
	{
		internalBuffer = new StringBuffer();
		nonWritableIndices = new ArrayList<Integer>();
		this.maximumSize = maximumSize;
	}
	
	/**
	 * <p>
	 * Returns the current length of the buffer i.e. the number of characters
	 * currently contained in the buffer.
	 * </p>
	 * 
	 * @return     The current size of the buffer.
	 */
	public synchronized int length() { return internalBuffer.length();}
	
	/**
	 * <p>
	 * Tests if there are no elements in the buffer i.e. if the  current
	 * {@link #length() length} of the buffer is less than or equal to zero.
	 * </p>
	 * 
	 * @return <code>True</code> if the {@link #length() length} of the buffer is 
	 * zero and <code>False</code> otherwise.
	 */
	public synchronized boolean isEmpty() {return internalBuffer.length()<=0;}

	/**
	 * <p>
	 * Overrides {@link #append(char, boolean)}, but forces the appended value
	 * to be writable. This means that the index of the appended character will be 
	 * return by {@link #getWritableIndices()}, and can be overwritten by another 
	 * {@link PasswordRequirement rule}. 
	 * </p>
	 * 
	 * @param c    The character to append to the buffer.
	 * 
	 * @return     <code>True</code> If the character was successfully appended to the
	 * buffer, and <code>False</code> otherwise. Generally, <code>False</code> is only 
	 * returned if the buffer is already full.
	 */
	public synchronized boolean append(char c) {return append(c, true);}	
	
	/**
	 * <p>
	 * Appends the specified character to the end of the buffer. This method 
	 * allows the caller to specify if the location to which {@link Character char}
	 * is written is read-only, or whether or not it can be {@link #overwrite(char, int) overwritten}
	 * later on. 
	 * </p>
	 * 
	 * @param c    The character to append to the buffer.
	 * @param overwritable A value of <code>False</code> will mark the index to which the 
	 * character is written as read-only, thus preventing further {@link #overwrite(char, int) updates} to that location. A value 
	 * of <code>True</code> on the other hand will permit the location to be {@link #overwrite(char, int)  overwritten}.
	 *  
	 * @return     <code>True</code> if the {@link Character character} was successfully added to the buffer
	 * and <code>False</code> otherwise. The buffer will not permit any further appends if it has 
	 * reached its maximum size, and will always return false in such a scenario. 
	 */
	public synchronized boolean append(char c, boolean overwritable)
	{
		boolean result;
		
		int changeIndex = internalBuffer.length();
		if (maximumSize>= 0 && changeIndex==maximumSize)
			result = false;
		else
		{		
			if (!overwritable) nonWritableIndices.add(changeIndex);
			internalBuffer.append(c);
			result = true;
		}
		
		return result;
	}
	
	/**
	 * <p>
	 * Writes a character to a specified index in the buffer. If the index is not empty, it will replace
	 * the current value at that location, provided the index is {@link #getWritableIndices() writable}.
	 * </p>
	 * 
	 * @param c    The character to write to the buffer.
	 * @param index        The index to which the character should be written.
	 * @param overwritable Indicates if the index should be marked {@link #getWritableIndices() read only}
	 * if the write is successful. In other words if the same index can be overwritten again by another 
	 * invocation of this method or {@link #overwrite(char, int)}.
	 * 
	 * @throws IllegalArgumentException If the specified index is read only or is beyond the 
	 * bounds of the buffer.
	 * 
	 * @see #overwrite(char, int)
	 */
	public synchronized void overwrite(char c, int index, boolean overwritable)
	{
		testBounds(index);
		if (nonWritableIndices.contains(index))
			throw new IllegalArgumentException("The index '" + index + "' is not writable!");
		
		if (!overwritable) nonWritableIndices.add(index);
		internalBuffer.setCharAt(index, c);
	}
	
	/**
	 * <p>
	 * Marks the element at the given index as read-only, to ensure that 
	 * it cannot be changed by any subsequent {@link PasswordRequirement#apply(PasswordBuffer) 
	 * rule invocations}.
	 * </p>
	 * @param index	The index to protect from {@link #overwrite(char, int) overwrites}.
	 */
	public synchronized void writeProtect(int index)
	{
		testBounds(index);
		if (!nonWritableIndices.contains(index))
			nonWritableIndices.add(index);
	}
	
	/**
	 * <p>
	 * Similar to {@link #overwrite(char, int, boolean)}, except that the index is always marked as writable.
	 * </p>
	 * 
	 * @param c    The new character to write to the buffer.
	 * @param index        The index to which the character should be written.
	 * 
         * @throws IllegalArgumentException If the specified index is read only or is beyond the 
         * bounds of the buffer.
         * 
         *  @see #overwrite(char, int, boolean)
	 */
	public synchronized void overwrite(char c, int index)
	{overwrite(c, index,true);}
	
	/**
	 * <p>
	 * Returns all the indices in the buffer which cannot be {@link #overwrite(char, int) written}  to. Note that the 
         * list will contain values in the range <code>0</code> to {@link #length()}. If the buffer is not limited 
         * to a maximum size, then it may still be possible to {@link #append(char) append} new values to it beyond the 
         * current {@link #length() size}.
	 * </p>
	 * 
	 * @return     A list of indices in the buffer which are protected from further {@link #overwrite(char, int) writes}.
	 * 
	 *  @see #getWritableIndices()
	 */
	public synchronized List<Integer> getNonWritableIndices() {return Collections.unmodifiableList(nonWritableIndices);}
	
	/**
	 * <p>
	 * Returns the first non-read-only index, counting from 0 to {@link #length() the current size}. The 
	 * return value of this method should be equal to the first value in the {@link List list} returned by 
	 * {@link #getWritableIndices()}. If there are no  {@link #overwrite(char, int) writable} indices in the 
	 * buffer, this method will return -1. 
	 * </p>
	 *   
	 * @return     The first index to which a value can be {@link #overwrite(char, int) written}  in the buffer
	 * if one exists, and -1 otherwise.
	 */
	public synchronized int getFirstOverwritableIndex()
	{
		int result ;
		
		List<Integer>	writableIndices = getWritableIndices();
		if (!writableIndices.isEmpty())
			result = writableIndices.get(0);
		else result = -1;
		
		return result;
	}
	
	/**
	 * <p>
	 * Returns a  {@link List list} of the indices in the buffer which can be  {@link #overwrite(char, int) written} to. Note that the 
	 * list will contain values in the range <code>0</code> to {@link #length()}. If the buffer is not limited 
	 * to a maximum size, then it may still be possible to {@link #append(char) append} new values to it beyond the 
	 * current {@link #length() size}.
	 * </p>
	 * 
	 * @return A {@link List list} of all the current indices in the buffer which can be {@link #overwrite(char, int) written} to. 
	 * 
	 * @see #getNonWritableIndices()
	 */
	public synchronized List<Integer>  getWritableIndices()
	{
		List<Integer> result  = new ArrayList<Integer>();		
		int upperbound = internalBuffer.length();		
		for (int i = 0;i<upperbound;i++)
		{
			if (!nonWritableIndices.contains(i)) result.add(i);
		}		
		return result;		
	}
	
	/**
	 * <p>
	 * Return the {@link Character character} at the specified index.
	 * </p>
	 *  
	 * @param index        The index of the character to return.
	 * @return     The character at the specified index.
	 * 
	 * @throws IllegalArgumentException If the index is beyond the bounds of the
	 * buffer.
	 */
	public synchronized char charAt(int index) 
	{
		testBounds(index);
		return internalBuffer.charAt(index);
	}
		
	@Override
	public synchronized String toString() {return internalBuffer.toString();}

	/**
	 * <p>
	 * Internal helper method which tests that a value falls between 0 and {@link #length() the length} of the
	 * buffer. 
	 * </p> 
	 * 
	 * @param index        The index to test.
	 */
	private void testBounds(int index)
	{
		if (index < 0 || index >= internalBuffer.length())
			throw new IllegalArgumentException("Invalid index. Must be within the range 0 to " + internalBuffer.length() +".");
		
		if (maximumSize==0)
			throw new IllegalArgumentException("Invalid index. Password is specified as zero length.");		
	}
}